/* 
 * Shuriken is the legal property of its developers. Please refer to the
 * COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections; 
using System.Collections.Generic;
using System.Diagnostics; 
using System.Text;
using System.Globalization; 

namespace Shuriken.Core.Utils
{
    /// <summary>
    /// Collects methods used for diagnostic purposes
    /// </summary>
    public class ApplicationDiagnostics
    {
        private LogFileTraceListener logFileTraceListener;
 
        public ApplicationDiagnostics(string logFilePath)
        {
            logFileTraceListener = new LogFileTraceListener(logFilePath);

            Trace.Listeners.Add(logFileTraceListener);
            Tracer.Level = TraceLevel.Info; 

            // Not listening to debug messages for performance reasons. 
            // Debug.Listeners.Add(logFileTraceListener);
        }

        /// <summary>
        /// Fixing Exception stack trace strings.
        /// </summary>
        /// <param name="msg">message to fix</param>
        /// <returns>fixed message</returns>
        /// <remarks>Fixing Exception stack trace strings by replacing new-line and tab characters with a customer symbol</remarks>
        private string FixStackTraceString(string msg) {
            string fixedMsg;
            fixedMsg = msg;

            // Removing new line 
            fixedMsg = fixedMsg.Replace("\r\n", ";;;");
            // Removing TAB
            fixedMsg = fixedMsg.Replace("\t", ":::");

            return fixedMsg;
        }

        public string FormatException(Exception excp) 
        {
            System.Text.StringBuilder sb = new StringBuilder();

            sb.Append("Message: ");
            sb.Append(excp.Message);
            sb.Append("\r\n");

            sb.Append("\r\n");
            sb.Append("Stack Trace: \r\n");
            sb.Append(excp.StackTrace.ToString());

            string result = FixStackTraceString(sb.ToString());
            return result;
        }

        /// <summary>
        /// Formats the exception for reporting 
        /// </summary>
        /// <param name="excp">Exception to format</param>
        /// <returns>string representation of exception</returns>
        public string FormatCriticalError(Exception excp) 
        {
            System.Text.StringBuilder sb = new StringBuilder();

            sb.Append("Product Version: ");
            sb.Append(ApplicationEnvironment.ProductVersion);
            sb.Append(" build ");
            sb.Append(ApplicationEnvironment.ProductBuildNumber);
            sb.Append(" ");
            sb.Append("File Version : ");
            sb.Append(ApplicationEnvironment.FileVersion);
            sb.Append(" ");
            sb.Append("OS Version: ");
            sb.Append(Environment.OSVersion.ToString());

            sb.Append(", ");
            sb.Append("Runtime Version: ");
            sb.Append(Environment.Version.ToString());

            sb.Append(", ");
            sb.Append(FormatException(excp));

            return sb.ToString();
        }

        /// <summary>
        /// Application trace level
        /// </summary>
        public TraceLevel TraceLevel 
        {
            get 
            {
                return Tracer.Level; 
            }
        }
    }

    public class Tracer 
    {
        public static TraceLevel Level; 

        [Conditional("TRACE")]
        public static void WriteError(string message, string category) 
        {
            System.Diagnostics.Trace.WriteLineIf(Level >= TraceLevel.Error, message, category); 
        }

        [Conditional("TRACE")]
        public static void WriteError(string message) {
            WriteError(message, "Error");
        }

        [Conditional("TRACE")]
        public static void WriteWarning(string message, string category) {
            System.Diagnostics.Trace.WriteLineIf(Level >= TraceLevel.Warning, message, category);
        }

        [Conditional("TRACE")]
        public static void WriteWarning(string message) {
            WriteWarning(message, "Warning");
        }

        [Conditional("DEBUG")]
        public static void WriteInfo(string message, string category) {
            System.Diagnostics.Debug.WriteLineIf(Level >= TraceLevel.Info, message, category);
        }

        [Conditional("DEBUG")]
        public static void WriteInfo(string message) {
            WriteInfo(message, "Info");
        }

        [Conditional("DEBUG")]
        public static void WriteVerbose(string message, string category) {
            System.Diagnostics.Debug.WriteLineIf(Level >= TraceLevel.Verbose, message, category);
        }

        [Conditional("DEBUG")]
        public static void WriteVerbose(string message) {
            WriteVerbose(message, "Verbose");
        }
    }

    /// <summary>
    /// A trace listener what is buffering the trace messages.
    /// </summary>
    public class LogFileTraceListener : System.Diagnostics.TraceListener 
    {
        private int sequenceNumber;
        private string logFilePath;


        public LogFileTraceListener(string logFilePath)
        {
            sequenceNumber = 0;
            this.logFilePath = logFilePath;
        }

        public override void Write(string message)
        {
            WriteEntry(message, null, null);
        }

        public override void Write(object o)
        {
            WriteEntry(o.ToString());
        }

        public override void Write(object o, string category)
        {
            WriteEntry(o.ToString(), category);
        }

        public override void Write(string message, string category)
        {
            WriteEntry(message, category);
        }

        public override void WriteLine(string message)
        {
            WriteEntry(message);
        }

        public override void WriteLine(object o)
        {
            WriteEntry(o.ToString());
        }

        public override void WriteLine(object o, string category)
        {
            WriteEntry(o.ToString(), category);
        }

        public override void WriteLine(string message, string category)
        {
            WriteEntry(message, category);
        }

        public override void Fail(string message)
        {
            this.WriteEntry(message, "Fail", Environment.StackTrace);
        }

        public override void Fail(string message, string detailMessage)
        {
            string msg; 

            if ((detailMessage == null) || (detailMessage.Length == 0))
            {
                msg = Environment.StackTrace;
            }
            else
            {
                msg = detailMessage;
            }

            this.WriteEntry(message, "Fail", msg);
        }

        private void WriteEntry(string message)
        {
            WriteEntry(message, null, null);
        }

        private void WriteEntry(string message, string category)
        {
            WriteEntry(message, category, null);
        }


        /// <summary>
        /// Adds a new entry to the trace listener
        /// </summary>
        /// <param name="message">message</param>
        /// <param name="category">category</param>
        /// <param name="stackTrace">stack trace</param>
        private void WriteEntry(string message, string category, string description)
        {
            if (message == null || (message.Length == 0))
            {
                message = "[No Message]";
            }

            if (category == null || (message.Length == 0))
            {
                category = "None";
            }

            LogEntry logEntry = new LogEntry(this.sequenceNumber++, category, message, description);

            try
            {
                using (System.IO.StreamWriter sw = System.IO.File.AppendText(logFilePath))
                {
                    sw.WriteLine(logEntry.ToString());
                }
            }
            catch (System.IO.IOException)
            {
                // Failed to write to the log file, but we do not do anything about this 
                // Swallow and business as usual
            }
        }
     
    }

    /// <summary>
    /// Entry for the buffered trace listener
    /// </summary>
    public class LogEntry
    {
        private static long starttick; 
        private int sequenceNumber;
        private DateTime dateTime;
        private string category;
        private string stackTrace;
        private string message;
        private long ticks;

        static LogEntry() 
        {
            starttick = DateTime.Now.Ticks; 
        }

        /// <summary>
        /// Constructorfor an entry
        /// </summary>
        /// <param name="sequenceNumber">Sequence number of the message</param>
        /// <param name="category">Category of the message</param>
        /// <param name="message">Message</param>
        /// <param name="stackTrace">Stack trace for the message</param>
        public LogEntry(int sequenceNumber, string category, string message, string stackTrace)
        {
            this.sequenceNumber = sequenceNumber;
            this.dateTime = DateTime.Now;
            this.category = (category == null) ? string.Empty : category;
            this.message = (message == null) ? string.Empty : message;
            this.stackTrace = (stackTrace == null) ? string.Empty : stackTrace;
            this.ticks = dateTime.Ticks - starttick;
        }

        /// <summary>
        /// Creates a string representation of the entry 
        /// </summary>
        /// <returns>string representation of the entry</returns>
        public override string ToString()
        {
            if (this.StackTrace.Length == 0)
            {
                return string.Format(CultureInfo.InvariantCulture, "{0}\t {1}\t {2}\t {3}\t {4}", new object[] { this.sequenceNumber, this.DateTimeString, this.ticks, this.Category, this.Message });
            }

            return string.Format(CultureInfo.InvariantCulture, "{0}\t {1}\t {2}\t {3}\t {4}\t {5}", new object[] { this.sequenceNumber, this.DateTimeString, this.ticks, this.Category, this.Message, this.StackTrace });
        }

        #region Properties
        /// <summary>
        /// Seqnece number for this entry
        /// </summary>
        public int SequenceNumber
        {
            get { return sequenceNumber; }
            set { sequenceNumber = value; }
        }

        /// <summary>
        /// Date and Time when the entry was created
        /// </summary>
        public DateTime DateTime
        {
            get { return dateTime; }
        }

        /// <summary>
        /// Date and Time for the entry in formatted string 
        /// </summary>
        public string DateTimeString
        {
            get 
            {
                return this.DateTime.ToString("hh:mm:ss:ff tt", CultureInfo.InvariantCulture);
            }
        }

        /// <summary>
        /// Category of this entry 
        /// </summary>
        public string  Category
        {
            get { return category; }
        }

        /// <summary>
        /// stack trace for this entry
        /// </summary>
        public string StackTrace
        {
            get { return stackTrace; }
        }

        /// <summary>
        /// Message for this entry
        /// </summary>
        public string Message
        {
            get { return message; }
        }
        #endregion 


    }
}
